package org.goj.practice.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.goj.common.entity.practice.Contest;
import org.goj.common.entity.practice.ContestProblem;
import org.goj.common.entity.practice.ContestUser;
import org.goj.practice.dto.ContestProblemDataDTO;
import org.goj.practice.dto.ContestProblemNumDTO;
import org.goj.practice.dto.ContestProblemPersonDTO;
import org.goj.practice.mapper.ContestMapper;
import org.goj.practice.mapper.ContestProblemMapper;
import org.goj.practice.mapper.ContestUserMapper;
import org.goj.practice.service.ContestProblemService;
import org.goj.practice.service.ContestService;
import org.goj.practice.service.ContestUserService;
import org.goj.practice.vo.ContestAdminVO;
import org.goj.practice.vo.ContestRankVO;
import org.goj.practice.vo.ContestVO;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>
 * 竞赛表 服务实现类
 * </p>
 *
 * @author CK
 * @since 2020-12-02
 */
@Service
public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> implements ContestService {

    @Resource
    private ContestMapper contestMapper;

    @Resource
    private ContestProblemService contestProblemService;

    @Resource
    private ContestProblemMapper contestProblemMapper;

    @Resource
    private ContestUserService contestUserService;

    @Resource
    private ContestUserMapper contestUserMapper;

    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public Page<Contest> contestListAdmin(Integer current, Integer size, Integer sort, String name) {
        Map<Integer, String> sortMap = Map.of(1, "contest_id", 2, "contest_id", 3, "start_time", 4, "start_time", 5, "end_time", 6, "end_time");
        Page<Contest> page = new Page<>(current, size);
        QueryWrapper<Contest> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("contest_id", "title", "start_time", "end_time", "defunct", "privilege")
                .and(StrUtil.isNotBlank(name), t -> t.like("title", name).or().like("description", name))
                .orderBy(sort != 0, (sort & 1) == 1, sortMap.get(sort));
        return super.page(page, queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addContest(Integer contestId, Integer userId, String title, List<Integer> problemIds, String startDateTime, String endDateTime, String description, Boolean defunct, List<Integer> languages, Integer privilege, String password, String numbers) throws ParseException {
        Contest contest = new Contest();
        if (contestId != -1) {
            contest.setContestId(contestId);
        }
        contest.setStartTime(SIMPLE_DATE_FORMAT.parse(startDateTime))
                .setEndTime(SIMPLE_DATE_FORMAT.parse(endDateTime));
        contest.setDefunct(defunct ? "Y" : "N").setDescription(description).setPrivilege(privilege)
                .setLanguage(languages.stream().mapToInt(t -> 1 << t).sum())
                .setTitle(title).setPassword(password).setUserId(userId);
        if (contestId == -1) {
            int contestNum = contestMapper.insertContest(contest);
            Assert.isTrue(contestNum == 1, "添加竞赛&作业失败");
        } else {
            int contestNum = contestMapper.updateById(contest);
            Assert.isTrue(contestNum == 1, "更新竞赛&作业失败");
        }

        if (contestId != -1) {
            QueryWrapper<ContestProblem> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("contest_id", contestId);
            boolean remove = contestProblemService.remove(queryWrapper);
            Assert.isTrue(remove, "更新竞赛&作业失败");
        }
        List<ContestProblem> contestProblemList = IntStream.range(0, problemIds.size()).mapToObj(i -> {
            ContestProblem contestProblem = new ContestProblem();
            contestProblem.setContestId(contest.getContestId()).setProblemId(problemIds.get(i))
                    .setTitle(title).setNum(i);
            return contestProblem;
        }).collect(Collectors.toList());
        boolean contestProblemNum = contestProblemService.saveBatch(contestProblemList);
        Assert.isTrue(contestProblemNum, "同步题目失败");

        if (privilege == 1 && StrUtil.isNotBlank(numbers)) {
            List<ContestUser> contestUserList = Arrays.stream(numbers.split("\n")).map(t -> {
                ContestUser contestUser = new ContestUser();
                contestUser.setContestId(contest.getContestId()).setNumber(t);
                return contestUser;
            }).collect(Collectors.toList());
            try {
                int contestUserNum = contestUserMapper.insertContestBatch(contestUserList);
                Assert.isTrue(contestUserNum > 0, "同步学号失败");
            } catch (Exception e) {
                e.printStackTrace();
                if (e instanceof DataIntegrityViolationException) {
                    Assert.isTrue(false, "列表中有学号不存在");
                } else {
                    Assert.isTrue(false, "同步学号失败");
                }
            }
        }
    }

    @Override
    public ContestAdminVO contestAdminById(Integer contestId) {
        ContestAdminVO contestAdminVO = new ContestAdminVO();
        Contest contest = super.getById(contestId);
        Assert.notNull(contest, "该竞赛&作业不存在");
        BeanUtils.copyProperties(contest, contestAdminVO);
        contestAdminVO.setStartDateTime(contest.getStartTime()).setEndDateTime(contest.getEndTime());
        QueryWrapper<ContestProblem> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("problem_id").eq("contest_id", contestId);
        contestAdminVO.setProblemIds(
                contestProblemService.list(queryWrapper).stream().map(t -> t.getProblemId().toString()).collect(Collectors.joining(","))
        );
        if (contest.getPrivilege() == 1) {
            QueryWrapper<ContestUser> queryWrapper1 = new QueryWrapper<>();
            queryWrapper1.eq("contest_id", contestId);
            contestAdminVO.setNumbers(
                    contestUserService.list(queryWrapper1).stream().map(ContestUser::getNumber)
                            .filter(StrUtil::isNotBlank).collect(Collectors.joining("\n"))
            );
        }
        return contestAdminVO;
    }

    @Override
    public Page<ContestVO> pageContest(Integer current, Integer size, Integer userId, String name, Integer selectStatusValue, Integer selectClassValue) {
        Page<ContestVO> page = new Page<>(current, size);
        QueryWrapper<ContestVO> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StrUtil.isNotBlank(name), "title", name)
                .eq(selectClassValue != -1, "cl.class_value", selectClassValue)
                .eq(selectStatusValue != -1 && selectStatusValue != 2, "privilege", selectStatusValue)
                .inSql(selectStatusValue == 2 && userId != -1, "contest.contest_id",
                        "SELECT contest_id FROM contest_user WHERE contest_user.user_id=" + userId)
                .orderByDesc("end_time");
        return contestMapper.pageContest(page, queryWrapper);
    }

    @Override
    public ContestRankVO contestRank(Integer contestId) {
        ContestRankVO contestRankVO = new ContestRankVO();
        List<ContestProblemNumDTO> contestProblemNum = contestProblemMapper.listContestProblemNum(contestId);
        contestRankVO.setProblemNum(contestProblemNum);
        List<ContestProblemDataDTO> contestProblemData = contestMapper.contestTableData(contestId);
        List<Map<String, Object>> collect = contestProblemData.stream().map(t -> {
            //此处循环获取每一个参与者的竞赛数据，连接数据库较为耗时，可考虑使用元编程或mybatis优化
            List<ContestProblemPersonDTO> contestProblemPerson = contestMapper.contestProblemPerson(contestId, t.getUserId());
            Map<String, Object> map = new HashMap<>(1);
            map.put("userId", t.getUserId());
            map.put("nick", t.getNick());
            map.put("score", t.getScore());
            int penalty = 0;
            for (ContestProblemPersonDTO problemPerson : contestProblemPerson) {
                if (problemPerson.getSubmit() > 0 && problemPerson.getResult() == 1) {
                    penalty += ((problemPerson.getSubmit() - 1) * 20 + problemPerson.getTime() / 60);
                }
            }
            map.put("penalty", penalty);
            for (ContestProblemNumDTO contestProblemNumDTO : contestProblemNum) {
                boolean flag = true;
                for (ContestProblemPersonDTO problemPerson : contestProblemPerson) {
                    if (contestProblemNumDTO.getNum().equals(problemPerson.getKNum())) {
                        map.put(contestProblemNumDTO.getNum(), problemPerson);
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    ContestProblemPersonDTO contestProblemPersonDTO = new ContestProblemPersonDTO();
                    contestProblemPersonDTO.setSubmit(0).setResult(0);
                    map.put(contestProblemNumDTO.getNum(), contestProblemPersonDTO);
                }
            }
            return map;
        }).sorted(Comparator.comparing(ContestServiceImpl::comparingByAc).reversed())
                .sorted(Comparator.comparing(ContestServiceImpl::comparingByPenalty))
                .collect(Collectors.toList());
        contestRankVO.setTableData(collect);
        return contestRankVO;
    }

    //以AC数排序
    private static Integer comparingByAc(Map<String, Object> map) {
        return (Integer) map.get("score");
    }

    //以罚时排序
    private static Integer comparingByPenalty(Map<String, Object> map) {
        return (Integer) map.get("penalty");
    }

}
